﻿using System;
using System.Net;
using System.Reflection;
using System.Xml;

namespace Abide.Classes
{
    public class Dropbox
    {
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public string DropboxFolderUrl
        {
            get { return dropBoxFolderUrl; }
            set { dropBoxFolderUrl = value; }
        }
        public string UpdateXmlFileName
        {
            get { return updateXmlFileName; }
            set { updateXmlFileName = value; }
        }
        public AssemblyName Latest
        {
            get { return latest == null ? null : new AssemblyName(latest.InnerText); }
        }
        public DateTime Date
        {
            get { return date == null ? DateTime.MinValue : DateTime.Parse(date.InnerText); }
        }
        public string UpdaterUrl
        {
            get { return updater == null ? null : updater.InnerText; }
        }
        public string Notes
        {
            get { return notes == null ? null : notes.InnerText; }
        }
        
        private string name;
        private string dropBoxFolderUrl;
        private string updateXmlFileName;
        private XmlDocument updateDocument;
        private XmlElement latest;
        private XmlElement date;
        private XmlElement updater;
        private XmlElement notes;

        public Dropbox() { }
        public Dropbox(string dropBoxFolderUrl, string name)
        {
            //Setup
            this.name = name;
            this.dropBoxFolderUrl = dropBoxFolderUrl;
            this.updateXmlFileName = "Update.xml";
        }
        public Dropbox(string dropBoxFolderUrl, string name, string updateXmlFileName)
        {
            //Setup
            this.name = name;
            this.dropBoxFolderUrl = dropBoxFolderUrl;
            this.updateXmlFileName = updateXmlFileName;
        }

        public bool Load()
        {
            try
            {
                //Load
                updateDocument = new XmlDocument();
                updateDocument.Load(Dropbox.Combine(dropBoxFolderUrl, updateXmlFileName));

                //Load Name Element
                XmlNode Name = updateDocument[name];

                //Get Nodes
                latest = Name["Latest"];
                date = Name["Date"];
                updater = Name["Updater"];
                notes = Name["Notes"];

                //Done
                return true;
            }
            catch { return false; } //Failed
        }
        public IAsyncResult BeginLoad(AsyncCallback callback, object state)
        {
            return new LoadDropboxAsyncResult(callback, this, state);
        }
        public bool EndLoad(IAsyncResult ar)
        {
            //Check
            if (ar is LoadDropboxAsyncResult)
                return ((LoadDropboxAsyncResult)ar).EndInvoke();
            else
                throw new ArgumentException("Unable to handle IAsyncResult. Type mismatch.", "ar");
        }

        internal class LoadDropboxAsyncResult : IAsyncResult
        {
            private readonly AsyncCallback callback;
            private System.Threading.ManualResetEvent waitHandle;
            private Exception excpetion;

            private object state;
            private Dropbox dropBox;
            private bool successful;

            private const int statePending = 0;
            private const int completeSync = 1;
            private const int completeAsync = 2;
            private int completeState = statePending;

            private bool CompletedSynchronously
            {
                get { return System.Threading.Thread.VolatileRead(ref completeState) == completeSync; }
            }
            public System.Threading.WaitHandle AsyncWaitHandle
            {
                get
                {
                    if (waitHandle == null)
                    {
                        bool done = IsCompleted;
                        System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(done);
                        if (System.Threading.Interlocked.CompareExchange(ref waitHandle, mre, null) != null)
                            mre.Close();
                        else if (!done && IsCompleted)
                            waitHandle.Set();
                    }

                    return waitHandle;
                }
            }
            public object AsyncState
            {
                get { return state; }
            }
            public bool IsCompleted
            {
                get { return System.Threading.Thread.VolatileRead(ref completeState) != statePending; }
            }

            public LoadDropboxAsyncResult(AsyncCallback callback, Dropbox dropBox, object state)
            {
                //Setup
                this.callback = callback;
                this.dropBox = dropBox;
                this.state = state;

                //Do Work
                System.Threading.ThreadPool.QueueUserWorkItem(Load, state);
            }
            private void Load(object state)
            {
                try
                {
                    //Load
                    dropBox.updateDocument = new XmlDocument();
                    dropBox.updateDocument.Load(Combine(dropBox.dropBoxFolderUrl, dropBox.updateXmlFileName));

                    //Load Name Element
                    XmlNode Name = dropBox.updateDocument[dropBox.name];

                    //Get Nodes
                    dropBox.latest = Name["Latest"];
                    dropBox.date = Name["Date"];
                    dropBox.updater = Name["Updater"];
                    dropBox.notes = Name["Notes"];

                    //Done
                    successful = true;
                }
                catch (WebException)
                {
                    successful = false;
                }
                catch (Exception ex)
                {
                    SetAsCompleted(ex, false);
                    successful = false;
                }
                finally
                {
                    SetAsCompleted(null, true);
                }
            }
            private void SetAsCompleted(Exception excpetion, bool completedSynchronously)
            {
                this.excpetion = excpetion;
                int previousState = System.Threading.Interlocked.Exchange(ref completeState, completedSynchronously ? completeSync : completeAsync);
                if (previousState != statePending)
                    throw new InvalidOperationException("Only one result can be retrieved.");
                if (waitHandle != null) waitHandle.Set();
                if (callback != null) callback(this);
            }
            public bool EndInvoke()
            {
                if (!IsCompleted)
                {
                    AsyncWaitHandle.WaitOne();
                    AsyncWaitHandle.Close();
                    waitHandle = null;
                }
                if (excpetion != null) throw excpetion;
                return successful;
            }
            bool IAsyncResult.CompletedSynchronously
            {
                get { throw new NotImplementedException(); }
            }
        }

        private static string Combine(string url, params string[] parts)
        {
            if (parts == null)
                return url;

            string combinedUrl = url;
            foreach (string part in parts)
                combinedUrl += string.Format("/{0}", part);

            return combinedUrl;
        }
    }
}
